package com.allchips.pin.util;

import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.allchips.pin.domain.OriginalStandardValue;
import com.allchips.pin.domain.PinInfoLc;
import com.allchips.pin.domain.StandardBrand;
import com.allchips.pin.dto.PinDTO;
import com.allchips.pin.dto.PinDiff;
import com.allchips.pin.dto.PinParamDTO;
import com.allchips.pin.service.OriginalStandardValueService;
import com.allchips.pin.service.PinInfoLcService;
import com.allchips.pin.service.StandardBrandService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Component
public class PinMatchUtil {

    static RestHighLevelClient highLevelClient;

    static JSONObject pinDt;

    static String index;

    @PostConstruct
    public void postConstruct() {
        PinMatchUtil.highLevelClient = SpringUtil.getBean(RestHighLevelClient.class);
    }

    @Value("${es.pin.index:}")
    public void setIndex(String index) {
        PinMatchUtil.index = index;
    }

    static {
        pinDt = JSONUtil.parseObj("{\"vrefn\":\"ref-\",\"vrefp\":\"ref+\",\"vssa\":\"avss\",\"vdda\":\"addv\",\"vref in/out\":\"ref\",\"gpio\":\"gp\",\"ep\":\"gnd\",\"pad\":\"gnd\",\"vio\":\"vddio\",\"vs+\":\"vss+\",\"v-\":\"vss-\",\"vs-\":\"vss-\",\"out\":\"vout\",\"output\":\"vout\",\"vo\":\"vout\",\"out1\":\"outa\",\"out2\":\"outb\",\"nic\":\"nc\",\"n.c\":\"nc\",\"pa3\":\"pa2\",\"pa4\":\"pa1\",\"a0\":\"pa0\",\"sd\":\"shutdown\",\"bypass\":\"byp\",\"vo1\":\"outn\",\"vo2\":\"outp\",\"von\":\"outn\",\"vop\":\"outp\",\"epad\":\"gnd\",\"shdn\":\"shutdown\",\"shnd\":\"shutdown\",\"in1-\":\"in-\",\"-input\":\"in-\",\"-\":\"in-\",\"vin-\":\"in-\",\"vin+\":\"in+\",\"+\":\"in+\",\"+input\":\"in+\",\"input+\":\"in+\",\"in1+\":\"in+\",\"+in\":\"in+\",\"input-\":\"in-\",\"vs\":\"vss+\",\"vcc\":\"vss+\",\"v+\":\"vss+\",\"vcc+\":\"vss+\",\"refout\":\"ref vout\",\"refout1\":\"ref outa\",\"eaout2\":\"ea outb\",\"eaout\":\"ea vout\",\"g1b\":\"g1bgainselect\",\"g1a\":\"g1bgainselect\",\"a12bc_n\":\"a12 bc_n\",\"cas_na15\":\"cas_n a15\",\"we_na14\":\"we_n a14\",\"ras_na16\":\"ras_n\",\"out4\":\"vout4\",\"outa\":\"vout1\",\"outb\":\"vout2\",\"out3\":\"vout3\",\"vin\":\"in+\",\"input\":\"in+\",\"sw\":\"switch output\"}");
    }


    private static PinDTO searchByPinNumberValue(PinParamDTO pinParamDTO) {
        final List<PinParamDTO.PinNumberValue> pinNumberValues = pinParamDTO.getPinNumberValues();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        TermQueryBuilder countQuery = QueryBuilders.termQuery("count", pinParamDTO.getCount());
        countQuery.boost(1);
        boolQueryBuilder.must(countQuery);
        for (final PinParamDTO.PinNumberValue pinNumberValue : pinNumberValues) {
            String key = pinNumberValue.getNumber();
            String value = pinNumberValue.getValue();
            String standardValue = standardValue(value);
            if (StrUtil.isBlank(standardValue)) {
                throw new RuntimeException(key + "对应值为空");
            }
            MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(key, standardValue);
            matchQueryBuilder.autoGenerateSynonymsPhraseQuery(false);
            matchQueryBuilder.minimumShouldMatch("1");
            matchQueryBuilder.operator(Operator.OR);
            matchQueryBuilder.analyzer("whitespace");
            countQuery.boost(1);
            boolQueryBuilder.must(matchQueryBuilder);
        }

        //如果品牌存在,品牌必填
        if (pinParamDTO.getStandardBrandId() != null) {
            TermsQueryBuilder partNumberTermQueryBuilder = QueryBuilders.termsQuery("standardBrandId", pinParamDTO.getStandardBrandId().toString());
            partNumberTermQueryBuilder.boost(1);
            boolQueryBuilder.must(partNumberTermQueryBuilder);
        }
        AggregationBuilder aggregationBuilder = AggregationBuilders.terms("standardBrandId").field("standardBrandId").size(20);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        final int end = PageUtil.getStart(ObjectUtil.defaultIfNull(pinParamDTO.getPageNo(), 0) - 1, ObjectUtil.defaultIfNull(pinParamDTO.getPageSize(), 30));
        searchSourceBuilder.from(end)
                .size(ObjectUtil.defaultIfNull(pinParamDTO.getPageSize(), 30))
                .sort(SortBuilders.scoreSort().order(SortOrder.DESC))
                .query(boolQueryBuilder)
                .aggregation(aggregationBuilder);
        final SearchResponse searchResponse = getSearchHits(index, searchSourceBuilder);
        return handleResult(searchResponse);
    }


    /**
     * 标准化值
     */
    private static String standardValue(String value) {
        //为空返回
        if (StrUtil.isBlank(value)) {
            return "";
        }
        value = value.toLowerCase();
        value = value.replace(" ", "");
        value = value.replace("(+)", "+").replace("(-)", "-");

        if (value.charAt(0) == '-' && value.charAt(value.length() - 1) == '-') {
            value = value.replace("(", " ").replace(")", " ").replace("/", " ").replace("[", " ").replace("]", " ").replace("  ", " ");
        } else {
            value = value.replace("(", " ").replace(")", " ").replace("/", " ").replace("[", " ").replace("]", " ").replace("  ", " ").replace("-", " ");
        }

        String[] splitValue = value.split(" ");
        StringBuilder stringBuffer = new StringBuilder();
        for (String s : splitValue) {
            if (StrUtil.isBlank(s)) {
                continue;
            }
            if (s.charAt(0) == '-' || s.charAt(0) == '+') {
                s = s.substring(1) + s.charAt(0);
            }
            OriginalStandardValueService originalStandardValueService = SpringUtil.getBean(OriginalStandardValueService.class);
            LambdaQueryWrapper<OriginalStandardValue> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(OriginalStandardValue::getOriginal,s);
            OriginalStandardValue originalStandardValueQueryData = originalStandardValueService.getOne(lambdaQueryWrapper);
            String standardStr = s;
            if (originalStandardValueQueryData!=null&&StrUtil.isNotBlank(originalStandardValueQueryData.getStandard())) {
                standardStr = originalStandardValueQueryData.getStandard();
            }
            standardStr = standardStr.replace("#", "").replace(".", "");
            if (stringBuffer.length() > 0) {
                stringBuffer.append(" ");
            }
            stringBuffer.append(standardStr);
        }
        return stringBuffer.toString();
    }

    /**
     * 存在型号，走型号搜索
     */
    private static PinDTO searchByPartNumber(PinParamDTO pinParamDTO) {
        final PinInfoLcService pinInfoLcService = SpringUtil.getBean(PinInfoLcService.class);
        LambdaQueryWrapper<PinInfoLc> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PinInfoLc::getPartNumber, pinParamDTO.getPartNumber());
        queryWrapper.isNotNull(PinInfoLc::getStandardPinDt);
        final List<PinInfoLc> list = pinInfoLcService.list(queryWrapper);
        if (CollUtil.isEmpty(list)) {
            return null;
        }
        PinInfoLc one = list.get(0);
        if (StrUtil.isBlank(one.getStandardPinDt())) {
            return null;
        }
        ThreadLocalUtil.get().set("pinInfoLc", one);
        JSONObject jsonObject = JSONUtil.parseObj(one.getStandardPinDt());
        if (jsonObject.size() == 0) {
            return null;
        }

        PinDTO pinDTO = pinShould(pinParamDTO, jsonObject);
        one.setPinDt(null);
        one.setStandardPinDt(null);
        pinDTO.setPinInfoLc(one);
        return pinDTO;
    }

    private static List<PinDiff> findOutDiff(Map<String, Object> sourceAsMap, JSONObject jsonObject) {
        List<PinDiff> diffList = new ArrayList<>();
        for (final Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            String key = entry.getKey();
            String value = Convert.toStr(entry.getValue());
            final String esValue = Convert.toStr(sourceAsMap.get(key));
            if (!diffValue(value, esValue)) {
                PinDiff pinDiff = new PinDiff();
                pinDiff.setNumber(key);
                pinDiff.setValue(value);
                pinDiff.setEsValue(esValue);
                diffList.add(pinDiff);
            }
        }
        return diffList;
    }

    private static boolean diffValue(String value, String esValue) {
        if (StrUtil.isBlank(value) || StrUtil.isBlank(esValue)) {
            return false;
        }
        final String[] s1 = value.split(" ");
        final String[] s2 = esValue.split(" ");
        Set<String> set1 = CollUtil.newHashSet(s1);
        set1 = set1.stream().map(String::toLowerCase).collect(Collectors.toSet());
        Set<String> set2 = CollUtil.newHashSet(s2);
        set2 = set2.stream().map(String::toLowerCase).collect(Collectors.toSet());
        final Set<String> xx = CollUtil.intersectionDistinct(set1, set2);
        return xx.size() > 0;
    }

    private static PinDTO handleResult(SearchResponse searchResponse) {
        PinDTO pin = new PinDTO();
        final PinParamDTO pinParam = (PinParamDTO) ThreadLocalUtil.get().get("pinParam");
        pin.setPageNo(ObjectUtil.defaultIfNull(pinParam.getPageNo(), 1));
        pin.setPageSize(ObjectUtil.defaultIfNull(pinParam.getPageSize(), 30));

        final SearchHits searchHits = searchResponse.getHits();
        if (searchHits == null) {
            return pin;
        }
        final long count = searchHits.getTotalHits().value;
        if (count == 0) {
            return pin;
        }
        pin.setTotalCount(Convert.toInt(count));

        Map<Integer, PinDTO.PinInfo> id2BeanMap = new HashMap<>();
        List<PinDTO.PinInfo> pinInfoList = new ArrayList<>();
        pin.setPinInfoList(pinInfoList);
        int number = PageUtil.getStart(pin.getPageNo() - 1, pin.getPageSize());
        for (final SearchHit searchHit : searchHits) {
            PinDTO.PinInfo pinDTO = new PinDTO.PinInfo();
            pinDTO.setId(++number);
            final Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
            pinDTO.setPartNumber(Convert.toStr(sourceAsMap.get("partNumber")));
            pinDTO.setStandardBrandId(Convert.toInt(sourceAsMap.get("standardBrandId")));
            pinDTO.setStandardBrandName(Convert.toStr(sourceAsMap.get("standardBrandName")));
            pinDTO.setKbaseCatId(Convert.toInt(sourceAsMap.get("kBaseCatId")));
            pinDTO.setCateName(Convert.toStr(sourceAsMap.get("cate")));
            pinDTO.setPacking(Convert.toStr(sourceAsMap.get("packing")));
            pinInfoList.add(pinDTO);
            final JSONObject jsonObject = (JSONObject) ThreadLocalUtil.get().get("dt");
            if (jsonObject != null) {
                pinDTO.setDiffContent(findOutDiff(sourceAsMap, jsonObject));
                if (CollUtil.isEmpty(pinDTO.getDiffContent())) {
                    pinDTO.setTip("pintopin");
                } else {
                    id2BeanMap.put(Convert.toInt(searchHit.getId()), pinDTO);
                    pinDTO.setTip("脚位相识");
                }
            }
        }
        //脚位差异原始提示
        if (CollUtil.isNotEmpty(id2BeanMap)) {
            PinInfoLc pinInfoLc = (PinInfoLc) ThreadLocalUtil.get().get("pinInfoLc");
            final PinInfoLcService bean = SpringUtil.getBean(PinInfoLcService.class);
            final List<PinInfoLc> pinInfoLcs = bean.listByIds(new ArrayList<>(id2BeanMap.keySet()));
            final JSONObject jsonObject = JSONUtil.parseObj(pinInfoLc.getPinDt());
            for (final PinInfoLc infoLc : pinInfoLcs) {
                dealOriginalDiff(infoLc, jsonObject, id2BeanMap);
            }
        }


        Aggregations aggregations = searchResponse.getAggregations();
        if (searchResponse.getAggregations() == null || aggregations.get("standardBrandId") == null) {
            return pin;
        }
        ParsedLongTerms aggregation = aggregations.get("standardBrandId");
        final List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
        if (CollUtil.isEmpty(buckets)) {
            return pin;
        }
        List<PinDTO.Brand> brandList = new ArrayList<>();
        pin.setBrandList(brandList);
        for (final Terms.Bucket bucket : buckets) {
            PinDTO.Brand brand = new PinDTO.Brand();
            brand.setId(Convert.toInt(bucket.getKey()));
            brand.setCount(Convert.toInt(bucket.getDocCount()));
            brandList.add(brand);
        }
        //反查品牌
        final List<Integer> brandIdList = brandList.stream().map(PinDTO.Brand::getId).collect(Collectors.toList());
        final StandardBrandService standardBrandService = SpringUtil.getBean(StandardBrandService.class);
        final List<StandardBrand> standardBrands = standardBrandService.listByIds(brandIdList);
        if (CollUtil.isEmpty(standardBrands)) {
            return pin;
        }
        final Map<Integer, StandardBrand> standardBrandMap = CollStreamUtil.toMap(standardBrands, StandardBrand::getId, Function.identity());
        for (final PinDTO.Brand brand : brandList) {
            final Integer id = brand.getId();
            if (!standardBrandMap.containsKey(id)) {
                continue;
            }
            brand.setName(standardBrandMap.get(id).getBrandName());
        }
        return pin;
    }

    /**
     * 设置原始值
     */
    private static void dealOriginalDiff(PinInfoLc infoLc, JSONObject jsonObject, Map<Integer, PinDTO.PinInfo> id2BeanMap) {
        final PinDTO.PinInfo pinInfo = id2BeanMap.get(infoLc.getId());
        final JSONObject originalJson = JSONUtil.parseObj(infoLc.getPinDt());
        final List<PinDiff> diffContent = pinInfo.getDiffContent();
        for (final PinDiff pinDiff : diffContent) {
            pinDiff.setOriginalValue(jsonObject.getStr(pinDiff.getNumber()));
            pinDiff.setOriginalEsValue(originalJson.getStr(pinDiff.getNumber()));
        }
    }


    /**
     * 产品型号匹配
     */
    public static PinDTO pinShould(PinParamDTO pinParamDTO, JSONObject jsonObject) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        TermQueryBuilder countQuery = QueryBuilders.termQuery("count", jsonObject.size());
        boolQueryBuilder.must(countQuery);
        for (Map.Entry<String, Object> stringObjectEntry : jsonObject.entrySet()) {
            String key = stringObjectEntry.getKey();
            String value = (String) stringObjectEntry.getValue();
            MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(key, value);
            matchQueryBuilder.autoGenerateSynonymsPhraseQuery(false);
            matchQueryBuilder.minimumShouldMatch("1");
            matchQueryBuilder.analyzer("whitespace");
            matchQueryBuilder.operator(Operator.OR);
            matchQueryBuilder.boost(1.0f);
            boolQueryBuilder.should(matchQueryBuilder);
        }

        //如果品牌存在,品牌必填
        if (pinParamDTO.getStandardBrandId() != null) {
            TermsQueryBuilder partNumberTermQueryBuilder = QueryBuilders.termsQuery("standardBrandId", pinParamDTO.getStandardBrandId().toString());
            partNumberTermQueryBuilder.boost(1);
            boolQueryBuilder.must(partNumberTermQueryBuilder);
        }
//        int minMatch = jsonObject.size() / 2;
//        if (minMatch == 0) {
//            minMatch = 1;
//        }
        boolQueryBuilder.minimumShouldMatch("30%");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        AggregationBuilder aggregationBuilder = AggregationBuilders.terms("standardBrandId").field("standardBrandId").size(20);
        final int end = PageUtil.getStart(ObjectUtil.defaultIfNull(pinParamDTO.getPageNo(), 0) - 1, ObjectUtil.defaultIfNull(pinParamDTO.getPageSize(), 30));
        searchSourceBuilder.from(end)
                .size(ObjectUtil.defaultIfNull(pinParamDTO.getPageSize(), 30))
                .sort(SortBuilders.scoreSort().order(SortOrder.DESC))
                .query(boolQueryBuilder)
                .aggregation(aggregationBuilder);
        final SearchResponse searchResponse = getSearchHits(index, searchSourceBuilder);

        ThreadLocalUtil.get().set("dt", jsonObject);
        return handleResult(searchResponse);
    }

    public static SearchResponse getSearchHits(String indexName, SearchSourceBuilder searchSourceBuilder) {
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchSourceBuilder.trackTotalHits(Boolean.TRUE);

        //https://www.elastic.co/guide/cn/elasticsearch/guide/current/relevance-intro.html#explain
        //输出 explain 结果代价是十分昂贵的，它只能用作调试工具 。千万不要用于生产环境。
        //builder.setExplain(true);
        log.info("index name:{}", indexName);
        log.info("执行查询语句:{}", searchSourceBuilder.toString());
        searchRequest.source(searchSourceBuilder);

        return getResponseData(searchRequest);
    }

    /**
     * 执行查询
     *
     * @param searchRequest
     * @return
     */
    public static SearchResponse getResponseData(SearchRequest searchRequest) {
        SearchResponse searchResponse = null;
        try {
            long startTime = System.currentTimeMillis();
            searchResponse = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            long endTime = System.currentTimeMillis();
            log.info("执行搜索查询时间:{}", (endTime - startTime));
        } catch (Exception e) {
            log.error("执行查询异常{}", e);
            e.printStackTrace();
        }
        return searchResponse;
    }

    public static PinDTO searchNew(PinParamDTO pinParamDTO) {
        if (StrUtil.isBlank(pinParamDTO.getPartNumber()) && CollUtil.isEmpty(pinParamDTO.getPinNumberValues())) {
            return null;
        }
        if (StrUtil.isNotBlank(pinParamDTO.getPartNumber())) {
            return searchByPartNumber(pinParamDTO);
        }

        return searchByPinNumberValue(pinParamDTO);
    }

    public static String updatePin(String id, String value) {
        String exceptionMsg = null;
        try {
            UpdateRequest updateRequest = new UpdateRequest(index, id);
            updateRequest.doc(value, XContentType.JSON);
            final UpdateResponse update = highLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("es更新异常", e);
            exceptionMsg = ExceptionUtil.getMessage(e);
        }
        return exceptionMsg;
    }

    public static String insertPin(String id, JSONObject jsonObject) {
        String exceptionMsg = null;
        try {
            IndexRequest indexRequest = new IndexRequest(index);
            indexRequest.id(id);
            indexRequest.source(jsonObject, XContentType.JSON);
            highLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("es更新异常", e);
            exceptionMsg = ExceptionUtil.getMessage(e);
        }
        return exceptionMsg;
    }
}
